package ru.compft.common.services.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import ru.compft.common.dao.UserDao;
import ru.compft.common.data.AppRole;
import ru.compft.common.data.AppUser;
import ru.compft.common.services.UserService;
import ru.compft.common.utils.CryptoUtils;

import javax.annotation.Resource;
import java.util.*;

/**
 * User: ASUS
 * Date: 31.05.12
 * Time: 23:54
 * Реализация сервиса аутентификации пользователей для security и сервиса работы с пользователями в приложении
 */
@Service
public class UserServiceImpl implements UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    @Resource(name = "userDao")
    private UserDao userDao;

    @Resource(name = "passwordEncoder")
    private MessageDigestPasswordEncoder encoder;

    /**
     * Метод нужен для аутентификации
     * Поскольку меняем поля пользователя, то коммитим транзакцию
     *
     * @param username
     * @return
     * @throws UsernameNotFoundException
     * @throws DataAccessException
     */
    @Transactional
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
        AppUser user = userDao.findUserByLogin(username);

        if (user == null)
            throw new UsernameNotFoundException("User not found: " + username);

        // Если пользователя нашли в БД, то обновим его дату активности
        user.setLastActivity(new Date());
        user = updateUser(user);
        return makeUser(user);
    }

    /**
     * Аутентификация пользователя, по логин/паролю
     * Поскольку меняем поля пользователя, то коммитим транзакцию
     *
     * @param login
     * @param password
     * @return
     */
    @Override
    @Transactional
    public AppUser authentication(String login, String password) {
        AppUser user = findUserByLogin(login);

        if (user == null)
            throw new UsernameNotFoundException("User not found: " + login);

        // теперь проверим правильность введенного пароля
        // шифруем введенный пароль
        final String cryptoPassw = CryptoUtils.encodePasswordWithMDEncoder(encoder, password, null);

        // если пароли не совпали - бросам ошибку
        if (!cryptoPassw.equals(user.getPassword()))
            throw new BadCredentialsException("Incorrect password!");

        // Обновим дату активности
        user.setLastActivity(new Date());
        user = updateUser(user);
        return user;
    }

    @Override
    public AppUser findUserById(Long userId) {
        AppUser user = userDao.findUserById(userId);

        if (user == null)
            throw new UsernameNotFoundException("User with ID {" + userId + "} not found");

        return user;
    }

    @Override
    public AppUser findUserByLogin(String login) {
        AppUser user = userDao.findUserByLogin(login);

        if (user == null)
            throw new UsernameNotFoundException("User with login {" + login + "} not found");

        return user;
    }

    @Override
    public List<AppUser> getAllUsers() {
        return userDao.getAllUsers();
    }

    @Override
    public List<AppUser> getUsers(int from, int count) {
        return userDao.getUsers(from, count);
    }

    @PreAuthorize("hasPermission(#appUser, 'WRITE')")
    public Boolean add(AppUser appUser) {
        // This will return true if it's accessible
        return true;
    }

    @PreAuthorize("hasPermission(#appUser, 'WRITE')")
    public Boolean edit(AppUser appUser) {
        // This will return true if it's accessible
        return true;
    }

    @PreAuthorize("hasPermission(#appUser, 'WRITE')")
    public Boolean delete(AppUser appUser) {
        // This will return true if it's accessible
        return true;
    }

    @Override
    public int countAllUsers() {
        return userDao.countAllUsers();
    }

    @Override
    @Transactional
    public AppUser addUser(AppUser newUser) {
        if (newUser != null) {
            // установим текущую дату регистрации
            final Date date = new Date();
            newUser.setRegDate(date);

            return userDao.persist(newUser);
        }
        return null;
    }

    @Override
    @Transactional
    public AppUser updateUser(AppUser user) {
        // todo здесь затираются роли и пароль !!!
        return userDao.update(user);
    }

    @Override
    @Transactional
    public Boolean deleteUser(AppUser appUser) {
        return userDao.delete(appUser);
    }

    @Override
    @Transactional
    public AppUser changePassword(AppUser user, String newPassword) {
        if (user != null) {
            // шифруем новый пароль
            final String cryptoPassw = CryptoUtils.encodePasswordWithMDEncoder(encoder, newPassword, null);

            // Находим пользователя в БД
            final AppUser appUser = findUserByLogin(user.getLogin());

            // меняем пароль и делаем update
            if (appUser != null) {
                appUser.setPassword(cryptoPassw);

                return userDao.update(appUser);
            }
        }

        return null;
    }

    @Override
    public boolean clearCache() {
        return true;
    }

    private User makeUser(AppUser user) {
        return new User(user.getLogin(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(),
                user.isAccountNonLocked(), makeGrantedAuthorities(user));
    }

    private Collection<GrantedAuthority> makeGrantedAuthorities(AppUser user) {
        final Set<GrantedAuthority> userAuthoritiesSet = new HashSet<GrantedAuthority>();

        final Set<AppRole> roles = user.getRoles();
        if (!CollectionUtils.isEmpty(roles)) {
            for (AppRole role : roles) {
                userAuthoritiesSet.add(new SimpleGrantedAuthority(role.getCode()));
            }
        }
        return userAuthoritiesSet;
    }
}
